Make executor mcp ensure a durable daemon and bridge to it#1196
Make executor mcp ensure a durable daemon and bridge to it#1196RhysSullivan wants to merge 1 commit into
Conversation
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-marketing | 37ff13e | Commit Preview URL Branch Preview URL |
Jun 29 2026, 04:28 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-cloud | 37ff13e | Jun 29 2026, 04:29 AM |
Cloudflare preview
Sign-in is Cloudflare Access (one-time PIN to an allowed email). The preview has its own database and encryption key; it is destroyed when this PR closes. |
| import { Client } from "@modelcontextprotocol/sdk/client/index.js"; | ||
| import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; | ||
| import { Effect } from "effect"; | ||
| import { mkdtempSync, readdirSync, rmSync } from "node:fs"; |
There was a problem hiding this comment.
readFileSync is used in stopAutoSpawnedDaemon but is not present in the node:fs import. At runtime this throws a ReferenceError, which the surrounding try/catch silently swallows, so the auto-spawned daemon is never sent SIGTERM. Subsequent rmSync still removes the data directory, leaving an orphan daemon process behind — accumulating across repeated test runs.
| import { mkdtempSync, readdirSync, rmSync } from "node:fs"; | |
| import { mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs"; |
@executor-js/cli
@executor-js/config
@executor-js/execution
@executor-js/sdk
@executor-js/codemode-core
@executor-js/runtime-quickjs
@executor-js/plugin-file-secrets
@executor-js/plugin-graphql
@executor-js/plugin-keychain
@executor-js/plugin-mcp
@executor-js/plugin-onepassword
@executor-js/plugin-openapi
executor
commit: |
executor mcp no longer starts a server in-process. It ensures a durable detached daemon and bridges stdio JSON-RPC to that owner over HTTP. Concurrent cold starts run a race-safe election: one process becomes the owner and the rest wait for its manifest and attach instead of failing. The owner's lifetime is independent of any MCP client, so many clients, the web UI, and the desktop app share one local server. This builds on the merged start-lock primitive so no client ever owns the database, replacing the earlier approach where the first mcp process started a server in-process and bridged to itself. Adds a cold-start election probe across the cli VM targets plus a local attach stress test; the one-winner, rest-attach behavior is verified on macOS, Linux, and Windows.
ee75f4a to
37ff13e
Compare
What
executor mcpno longer owns the local database. It ensures a durable, detached daemon and bridges stdio JSON-RPC to it over HTTP. Concurrent cold starts run a race-safe election: exactly one process becomes the owner, and the rest wait for its manifest and attach rather than failing. The owner's lifetime is independent of any MCP client, so multiple MCP clients, the web UI, and the desktop app all share one local server.Supersedes #1033, which had the first
executor mcpprocess start a server in-process and bridge to itself. That tied the shared owner's lifetime to a transient client: when it exited, the server everyone else attached to went down. This builds on the merged start-lock primitive instead, so no client ever owns the database.Tests
e2e/local/cli-mcp-daemon-attach-stress.test.ts: cold-start race, attach storm, and kill-under-load against the local dev server.e2e/cli/election-cold-start.test.ts: fires N simultaneous clients at one cold data dir on the cli VM targets and asserts exactly one daemon is elected and every client attaches.e2e/cli/election-cold-start.win.ps1: the same election proven on real Windows.Verified the one-winner, rest-attach behavior on macOS, Linux, and Windows:
(cold: one client spawns the daemon, the other five attach, all round-trip; warm: all six attach, none spawn.)